#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <dfs.h>
#include <errno.h>
#include <limits.h>
#include "dfs_net.h"
#include "dfs_service.h"
#include "dfs_multies.h"
#include "dfs_messages.h"
#include "dfs_transfer_file_send.h"
#include "dfs_transfer_file_recv.h"

void dfs_net_module(){
	extern fd_set master_list;
	extern int fdmax;
	extern char this_address[MAXIPADDRESSLEN];
	
	/* temporary file descriptor list */
	fd_set temp_list;
		
	/* File Descriptors for this server ,it's connected peers & local ipc */
	int p2p_fd;
	int client_fd;
	
	/* write/read usefull variables */
	int nbytes;
	char buffer[BUFFER_SIZE];
	
	/* various local variables */
	int i,j,result;
	
	/* signal setup */
	signal(SIGUSR1,catch_USR1_P);
	
	/* socket setup */	
	p2p_fd = socket_addr_setup_net(P2P_PORT);
	client_fd = socket_addr_setup_net(SERVICE_PORT);
	
	/* add p2p fd and pipe fd to sets in order to select() */
	FD_SET(p2p_fd,&master_list);
	FD_SET(client_fd,&master_list);
	
	/* keep track of the biggest fd */
	fdmax = (p2p_fd >= client_fd)?p2p_fd:client_fd;

	/* a syscall here to give DFS the service pid */
	result = dfs_set_net_components(getpid());
	if (result != OK){
		fprintf(stderr,"Error.An error occured while registering the service component.\n");
		exit(-1);
	}else
		printf("[P2P][P2P network module has been registerd to the DFS server (%d)]\n",getpid());
	
	/*extract ip address from local hostfile and store it in a variable */
	extract_ip_address(this_address);
	
	/* main loop */
	while(TRUE){
		temp_list = master_list;
		if (select(fdmax+1, &temp_list, NULL, NULL, NULL) == -1) {
			perror("Error in select()");
			exit(errno);
		}
		
		/* run through existing connections looking for data to read */
		for (i=0; i<=fdmax; i++) {
			if(FD_ISSET(i,&temp_list)){
				if (i == p2p_fd){
					/* handle new p2p connections */
					p2p_accept_connection(p2p_fd);
				}else if(i == client_fd){
					/* handle new client connections */
					client_accept_connection(client_fd);
				}else{
					/* handle data from a client */
					read_from_fd(i,buffer,"Error while Reading");
					if(nbytes <= 0){
						if(FD_ISSET(i,&client_list))
							FD_CLR(i,&client_list);
						if(FD_ISSET(i,&p2p_list))
							FD_CLR(i,&p2p_list);
						FD_CLR(i,&master_list);
						close(i);
					}else{
						/* we got some data */
						dfs_message m_in,m_out;
						int r;
						
						printf("inMSG:%s\n",buffer);
						if(string_to_message(&m_in,buffer) != ERROR){
							
							if(FD_ISSET(i,&client_list)) printf("[SERVICE]");
							else printf("[P2P]");
							memset(buffer,0,BUFFER_SIZE);
							r = handle_in_message(&m_in,buffer,i);
							if(r == OK_MSG){ 
								reply_with_OK_message(&m_out,m_in.sender,buffer,i);
								printf("[Sending OK message]\n");
							}else if(r == ER_MSG){ 
								reply_with_ER_message(&m_out,m_in.sender,buffer,i);
								printf("[Sending ERROR message]\n");
							}else if(r == RR_MSG){
								printf("[Respond to %s request]\n",m_in.sender);
								reply_with_RRESPONCE_message(&m_out,m_in.sender,buffer,i);
							}else if(r == UNKNOWN_MSG){
								printf("[Last message received was invalid or could not been \
										understood.Sending ERROR message]\n");
								reply_with_UNKNOWN_message(&m_out,m_in.sender,buffer,i);
							}
						}else{
							printf("[Bad format of a recently received message]\n");
						}
						printf("outMSG:%s\n",buffer);
					}
					memset(buffer,0,BUFFER_SIZE);
				}/* end of handling client data */
			}/* end of handling a new connection/data */
		}/* end of running through connections*/
	}/* end of main loop*/
} /*end of dfs_net */

/* Function that setups socket & address info */
int socket_addr_setup_net(int port){
	/* Socket address structure for remote address & length */
	int sd,i;
	struct sockaddr_in sa;

	/* opening p2p socket*/
	sd = socket(AF_INET,SOCK_STREAM,0);
	if(sd < 0){
		perror("Error while opening socket");
		exit(errno);
	}

	/* setting local address and bind it. Server can bind ports inside range [P2P_PORT,P2P_PORT+4] */
	memset(&sa,0,sizeof(sa));
	sa.sin_family = AF_INET;
	sa.sin_addr.s_addr = INADDR_ANY;
	for(i=0;i<MAX_BIND_ATTEMPTS;i++){
		sa.sin_port = htons(port + i);
		if(bind(sd, (struct sockaddr *) &sa, sizeof(sa)) < 0)
			continue;
		else{
			if(port == P2P_PORT)
				printf("[P2P]");
			else if(port == SERVICE_PORT)
				printf("[SERVICE]");
			printf("[Server bind at port %d]\n",port + i);
			break;
		}
	}
	if(i >= MAX_BIND_ATTEMPTS){
		perror("Error while binding socket");
		exit(errno);
	}
	
	/* setting listening for this one */
	if(listen(sd,LISTENQUEUE) < 0){
		perror("Error while listening to socket");
		exit(errno);
	}
	return sd;
}

/* Catches SIGUSR1 signal to initiate exiting procedure */
void catch_USR1_P(int s){
	extern fd_set master_list;
	extern int fdmax;
	int i;
	
	/* closing all sockets */
	for(i=0;i<=fdmax;i++){
		if(FD_ISSET(i,&master_list)){
			FD_CLR(i,&master_list);
			close(i);
		}
	}
	printf("[P2P][Module has terminated]\n");
	
	/* exiting */
	exit(0);
		
}

/* Function that accept p2p connections */
void p2p_accept_connection(int sd){
	extern fd_set master_list;
	extern int fdmax;
	int remote_fd;
	struct sockaddr_in remoteaddr;
	socklen_t remote_addrlen;
	
	/* set remote address structure */
	remote_addrlen = sizeof(remoteaddr);
	remote_fd = accept(sd, (struct sockaddr *) &remoteaddr, &remote_addrlen);
	if(remote_fd < 0) perror("Error while accepting new connection");
	else {
		/* if no error occured put it in master list of fds */
		if(dfs_config_neighbours(ACT_CONNECTING,inet_ntoa(remoteaddr.sin_addr),remote_fd,NULL) != OK){
			close(remote_fd);
			printf("[P2P][Could not add %s to my neighbours]\n",remote_fd);
		}
		FD_SET(remote_fd, &master_list);
		if(remote_fd > fdmax) fdmax = remote_fd;
		
		FD_SET(remote_fd, &p2p_list);
		if(remote_fd > fdmax) p2p_fdmax = remote_fd;
		printf("[P2P][Server got new connection from IP address %s . Socket %d]\n",inet_ntoa(remoteaddr.sin_addr),remote_fd);
	}
}

/* Extracts IP address of a server in a network . gethostbyname did not work */
void extract_ip_address(char *ipaddr){
	FILE *fp;
	int len;
	
	memset(ipaddr,0,MAXIPADDRESSLEN);
	fp = fopen("/etc/hostname.file","r");
	if(fp!=NULL){
		fread(ipaddr,sizeof(char),MAXIPADDRESSLEN,fp);
		len = strlen(ipaddr);
		ipaddr[len-1]='\0';
		printf("[P2P & SERVICE][IP address is %s]\n",ipaddr);
	}else{
		fprintf(stderr,"Error while opening \"/etc/hostname.file\"\n");
		exit(-1);
	}
}